package com.beta.backend.gateway.filter;

import com.beta.backend.gateway.config.GatewayCfgBean;
import com.beta.backend.gateway.resolver.UserKeyResolver;
import com.beta.cat.service.RedissonService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RHyperLogLog;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.NettyWriteResponseFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * 访问量统计，只统计一个月以内的。
 *
 * @author taofucheng
 * @date 2024/4/25
 */
@Component
public class ViewStatGatewayFilter implements GlobalFilter, Ordered {
    /**
     * 保留多久内的流量记录
     */
    private static final Duration expire = Duration.ofDays(32);//超过一个月，为的是可以查询到上个月的数据
    private static Pattern ipv4Pattern = Pattern.compile("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}");
    /**
     * 格式化器
     */
    public static final DateTimeFormatter hourFomater = DateTimeFormatter.ofPattern("yyyy-MM-dd HH");
    public static final DateTimeFormatter dayFomater = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    public static final DateTimeFormatter monthFomater = DateTimeFormatter.ofPattern("yyyy-MM");
    private static final String BASE = "backend-gateway:";
    private static final String PV_PREFIX = BASE + "PV:";
    private static final String UV_PREFIX = BASE + "UV:";
    private static final String DAU_PREFIX = BASE + "DAU:";//日活
    private static final String MAU_PREFIX = BASE + "MAU:";//月活
    @Autowired
    private RedissonService redissonService;
    @Autowired
    private GatewayCfgBean configBean;
    @Autowired
    private UserKeyResolver userKeyResolver;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        long startTime = System.currentTimeMillis();
        return chain.filter(exchange)
                .doOnSuccess(aVoid -> stat(exchange, startTime))
                .doOnError(throwable -> stat(exchange, startTime));
    }

    /**
     * 统计各类信息
     *
     * @param exchange
     * @param startTime
     */
    private void stat(ServerWebExchange exchange, long startTime) {
        if (!configBean.isStatOpen()) {
            return;//不做任何事情
        }
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        String userId = userKeyResolver.getUserId(exchange);

        statPv(request, response);
        statUv(request, response, userId);
    }

    private void statPv(ServerHttpRequest request, ServerHttpResponse response) {
        String uri = request.getURI().getPath();
        Integer code = response.getRawStatusCode();

        incr(getPvHourKey(null, 0));
        incr(getPvDayKey(null, 0));
        incr(getPvMonthKey(null, 0));

        String codeStr = code == null ? "-" : code.toString();
        String codeKey = getHttpCodesKey();
        redissonService.setAdd(codeKey, codeStr);//把所有的code放到set中，方便查看时查找
        redissonService.expire(codeKey, expire);

        incr(getPvHourKey(codeStr, 0));
        incr(getPvDayKey(codeStr, 0));
        incr(getPvMonthKey(codeStr, 0));
    }

    private void statUv(ServerHttpRequest request, ServerHttpResponse response, String userId) {
        userId = StringUtils.defaultIfBlank(userId, getClientId(request));

        RHyperLogLog<Object> hyperLogLog = redissonService.getRedissonClient().getHyperLogLog(getUvHourKey(0));
        hyperLogLog.add(userId);
        hyperLogLog.expire(expire.toHours(), TimeUnit.HOURS);

        hyperLogLog = redissonService.getRedissonClient().getHyperLogLog(getDAUKey(0));
        hyperLogLog.add(userId);
        hyperLogLog.expire(expire.toHours(), TimeUnit.HOURS);

        hyperLogLog = redissonService.getRedissonClient().getHyperLogLog(getMAUKey(0));
        hyperLogLog.add(userId);
        hyperLogLog.expire(expire.toHours(), TimeUnit.HOURS);
    }

    public static String getMAUKey(int back) {
        LocalDateTime now = LocalDateTime.now();
        if (back > 0) {
            now = now.minusMonths(back);
        }
        return MAU_PREFIX + now.format(monthFomater);
    }

    public static String getDAUKey(int back) {
        LocalDateTime now = LocalDateTime.now();
        if (back > 0) {
            now = now.minusDays(back);
        }
        return DAU_PREFIX + now.format(dayFomater);
    }

    public static String getUvHourKey(int back) {
        LocalDateTime now = LocalDateTime.now();
        if (back > 0) {
            now = now.minusHours(back);
        }
        return UV_PREFIX + "HOUR:" + now.format(hourFomater);
    }

    @Override
    public int getOrder() {
        return NettyWriteResponseFilter.WRITE_RESPONSE_FILTER_ORDER;
    }

    /**
     * 获取指定的key
     *
     * @param codeStr 状态码，如果指定，则表示这是状态码记录，不指定，表示记录总数
     * @param back    当前时间前移动的小时数
     * @return
     */
    public static String getPvHourKey(String codeStr, int back) {
        LocalDateTime now = LocalDateTime.now();
        if (back > 0) {
            now = now.minusHours(back);
        }
        String key = PV_PREFIX + "HOUR:" + now.format(hourFomater);
        if (StringUtils.isNotBlank(codeStr)) {
            key += ":CODE:" + codeStr;
        }
        return key;
    }

    /**
     * 获取指定的key
     *
     * @param codeStr 状态码，如果指定，则表示这是状态码记录，不指定，表示记录总数
     * @param back    当前时间前移动的天数
     * @return
     */
    public static String getPvDayKey(String codeStr, int back) {
        LocalDateTime now = LocalDateTime.now();
        if (back > 0) {
            now = now.minusDays(back);
        }
        String key = PV_PREFIX + "DAY:" + now.format(dayFomater);
        if (StringUtils.isNotBlank(codeStr)) {
            key += ":CODE:" + codeStr;
        }
        return key;
    }

    /**
     * 获取指定的key
     *
     * @param codeStr 状态码，如果指定，则表示这是状态码记录，不指定，表示记录总数
     * @param back    当前时间前移动的月数
     * @return
     */
    public static String getPvMonthKey(String codeStr, int back) {
        LocalDateTime now = LocalDateTime.now();
        if (back > 0) {
            now = now.minusMonths(back);
        }
        String key = PV_PREFIX + "MONTH:" + now.format(monthFomater);
        if (StringUtils.isNotBlank(codeStr)) {
            key += ":CODE:" + codeStr;
        }
        return key;
    }

    /**
     * 获取http的响应状态码
     *
     * @return
     */
    public static String getHttpCodesKey() {
        return PV_PREFIX + "HTTP_CODES";
    }

    /**
     * 将redis中指定的值累加1
     *
     * @param key
     */
    private void incr(String key) {
        boolean isFirst = false;
        if (StringUtils.isBlank(redissonService.getStr(key))) {
            isFirst = true;
        }
        redissonService.incr(key);
        if (isFirst) {
            redissonService.expire(key, expire);
        }
    }

    /**
     * 读取请求的IP信息
     *
     * @param req
     * @return
     */
    public static String getClientId(ServerHttpRequest req) {
        String ret = "Unknown";
        try {
            String sessionId = req.getHeaders().getFirst("sessionId");
            if (StringUtils.isNotBlank(sessionId)) {
                return sessionId;
            }
            String ipAddress = "";
            String[] headers = {"X-Real-IP", "x-forwarded-for", "X-Forwarded-For", "Proxy-Client-IP", "WL-Proxy-Client-IP"};
            String unknown = "unknown";
            for (String header : headers) {
                ipAddress = req.getHeaders().getFirst(header);
                if (StringUtils.isBlank(ipAddress) || unknown.equalsIgnoreCase(ipAddress)) {
                    continue;
                }
                ret = ipAddress;
                //如果追加了多个ip，找出其中的公网ip
                String[] ips = ipAddress.split(",");
                String tmp = ips[ips.length - 1];
                for (String ip : ips) {
                    if (StringUtils.isBlank(ip)) {
                        continue;
                    }
                    ip = StringUtils.trim(ip);
                    if (isLocalIp(ip)) {
                        continue;
                    }
                    tmp = ip;
                    break;
                }
                ret = tmp;
                if (!isLocalIp(ret)) {
                    break;
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return ret;
    }

    private static boolean isLocalIp(String ip) {
        if (!ipv4Pattern.matcher(ip).matches()) {
            return true;//不是IP地址，一律认为不是公网
        }
        if (ip.startsWith("127.0.0.1") || ip.startsWith("10.") || ip.startsWith("192.168.") || ip.startsWith("172.")) {
            if (ip.startsWith("172.")) {
                //172的局域网范围是：172.16~172.31
                try {
                    int part = Integer.parseInt(ip.split("\\.")[1].trim());
                    if (part > 15 && part < 32) {
                        return true;
                    }
                } catch (Exception e) {
                }
            } else {
                return true;//局域网IP不需要
            }
        }
        return false;
    }
}
